#pragma once

#include <iostream>
#include <functional>
#include <utility>
#include <vector>
#include <unordered_map>
#include <assert.h>
#include "Sock.hpp"
#include "log.hpp"
#include "Epoll.hpp"
#include "Protocol.hpp"

class Connection;
using func_t = std::function<void(Connection *)>;

class TcpServer;
using callback_t = std::function<void(Connection *, std::string &)>;

// 解释这个类：TCP Server里会维护大量的Connection，每获取一个连接，不再是简简单单的使用sock，而是对其new一个Connection对象，并填入相关信息。
class Connection
{
public:
    Connection(int sock = -1)
        : _sock(sock), _ptsv(nullptr)
    {
    }

    // 一个客户端连接通常面临三类事件：读、写、异常。因此，我们为每个socket连接，设置了三个回调函数，分别用于响应这三种事件。
    // 这些回调函数的具体实现由上层逻辑决定，Connection类无需维护这些实现的细节，只需在相应事件发生时调用对应的回调函数即可。
    void SetCallBack(func_t recv_cb, func_t send_cb, func_t except_cb)
    {
        _recv_cb = recv_cb;
        _send_cb = send_cb;
        _except_cb = except_cb;
    }

public:
    // 用于进行IO的文件描述符
    int _sock;

    // 三个回调方法，表征的就是对_sock进行特定读写对应的方法
    func_t _recv_cb;   // 读回调
    func_t _send_cb;   // 写回调
    func_t _except_cb; // 异常回调

    // 每个sock都需要有属于自己的接收/发送缓冲区（使用string的这种写法，目前无法处理二进制流，只是针对文本）
    std::string _inbuffer;  // 接收缓冲区/输入缓冲区
    std::string _outbuffer; // 发送缓冲区/输出缓冲区

    // 设置对Tcp服务器的回值指针（后续有用）
    TcpServer *_ptsv;
};

// 即，这个TCP服务器是基于reactor模式设计的。
class TcpServer
{
    // const修饰的类的静态成员变量，可以在类内初始化
    const static int default_port = 9090;    // 默认端口号
    const static int default_revs_num = 128; // 默认的就绪事件集最大数量
public:
    TcpServer(int port = default_port)
        : _port(port), _revs_num(default_revs_num)
    {
        // 1、这是网络套接字部分：
        _listensock = Sock::Socket();   // a、创建套接字
        Sock::Bind(_listensock, _port); // b、绑定
        Sock::Listen(_listensock);      // c、监听

        // 2、这是epoll多路转接部分：
        _poll.CreateEpoll(); // a、创建多路转接对象

        AddConnection(_listensock, std::bind(&TcpServer::Accepter, this, std::placeholders::_1), nullptr, nullptr); // b、添加listensock到服务器中：监听sock只用于接受客户端连接（只负责读取），并不关心写入和异常

        // 语法解释：构造函数的函数体内，能使用该类的成员函数（因为初始化列表结束，该对象就生成了）
        // 语法解释：C++11，这里的写法涉及function包装器对类内非静态成员函数的使用，以及如何使用bind解决该问题。

        _revs = new struct epoll_event[_revs_num]; // c、构建存放就绪事件的事件集
    }

    ~TcpServer()
    {
        if (_listensock >= 0)
            close(_listensock);
        if (_revs)
            delete[] _revs;
    }

    // 解释该函数的作用：为什么需要单独拎出？
    // 要知道，除了最初的_listensock，未来服务器会连接大量的客户端socket，而每一个sock都必须被封装成为一个Connection，且每一个sock都需要交付给epoll监测
    // 当服务器中存在大量的Connection的时候，TcpServer就需要将所有的Connection要进行管理，而管理的方式是“先描述，在组织”，描述我们有Connection对象，如何组织？
    // 自然是需要建立一个sock与其对应Connection的集合体，由此才有了TCPServer中的映射表std::unordered_map<int, Connection *> _connections，这里不使用这个结构也行的，关键在于要理解为什么需求这个表。
    // 上述是一系列前因，为什么要有这个函数？就是因为这些大量的serversock和listensock一样，对每一个到来的sock，都需要完成如下相同的步骤操作，因此，我们将其封装成一个函数，专门针对任意sock进行添加TcpServer。
    void AddConnection(int sock, func_t recv_cb, func_t send_cb, func_t except_cb)
    {
        // 0、ET模式，soke需要设置为非阻塞状态
        Sock::SetNonBlock(sock);

        // 1、构建conn对象，封装sock
        Connection *conet = new Connection(sock);
        conet->SetCallBack(recv_cb, send_cb, except_cb);
        conet->_ptsv = this;

        // 2、将sock添加到epoll中进行监管
        _poll.AddFromEpoll(sock, EPOLLIN | EPOLLET); // 解释这里需要监管事件：多路转接的服务器，一般默认只打开对读取事件的关心，而写入事件则按需打开

        // 3、不要忘记Tcpserver中用于维护映射关系的Connection映射表
        _connections.insert(std::make_pair(sock, conet));
    }

    // 上层调取：根据就绪的事件，进行特定事件的派发
    void Dispather(callback_t call)
    {
        _call = call; // 设置上层的业务处理函数
        while (true)
        {
            loopOnce(); // 单次事务处理
        }
    }

    void loopOnce()
    {
        int n = _poll.WaitEpoll(_revs, _revs_num); // 从epoll中捞取就绪事件集

        for (int i = 0; i < n; ++i) // 挨个处理：细节，如果底层没事件就绪/等待超时，那么此处n=0，是不会进入循环的
        {
            int sock = _revs[i].data.fd;
            uint32_t revents = _revs[i].events;

            // 统一将所有异常交给read或write处理。
            if (revents & EPOLLERR)// EPOLLERR：文件描述符上发生了错误
                revents |= (EPOLLIN | EPOLLOUT); 
            if (revents & EPOLLHUP)// EPOLLHUP: 表示文件描述符上的连接已经挂起（例如，TCP连接被对方关闭）。
                revents |= (EPOLLIN | EPOLLOUT); 

            // 这里，不用判断就绪的sock是监听sock还是普通sock，因为我们建立了connection体系，其中就包含有这些sock对应需要的方法（即：listensock会去调用Accepter，serversock会调用receiver、Sender、Excepter）
            if (revents & EPOLLIN) // 读事件就绪
            {
                // 为什么需要判断sock是否存在：
                // 1、验证其合法性。比如，我们这种写法中，有可能读写事件都就绪了，但某次读事件读取失败，导致跳转调用了异常事件，将sock关闭了。那么后续写事件处，就会因为sock不合法而不执行。
                // 2、从代码角度，后续_connections[sock]处要调用，就需要保证->合法。
                if (IsConnectionExist(sock) && _connections[sock]->_recv_cb)
                    _connections[sock]->_recv_cb(_connections[sock]); // 注意这里unordered_map::operator[]的返回值含义。
            }
            if (revents & EPOLLOUT) // 写事件就绪
            {
                if (IsConnectionExist(sock) && _connections[sock]->_send_cb)
                    _connections[sock]->_send_cb(_connections[sock]);
            }
        }
    }

    bool IsConnectionExist(int sock)
    {
        auto iter = _connections.find(sock);
        if (iter != _connections.end())
            return true;
        return false;
    }

    void Accepter(Connection *conet)
    {
        // 来到此处，我们可以保证的是此时底层一定有连接事件就绪。本次读取，accepte不会被阻塞。
        logMessage(DEBUG, "Accepter be called. sock is %d", conet->_sock);

        // 为什么要循环监听：你怎么保证，底层只有一个连接就绪呢？
        // epoll从底层捞取就绪事件，有可能在一次调用中捞取到多个就绪的连接事件。ET模式下，如何保证本轮捞取完全？需要不断循环一直捞取，直到accept返回失败为止。
        // 我们设置循环，有可能底层就只有一个连接就绪，那后续循环中accept难道不会阻塞吗？不会,因为我们已经将监听socket设置为NONBLOCK（非阻塞）模式,如果没有连接请求，accept函数会立即返回一个错误码.
        while (true)
        {
            std::string clientip;
            uint16_t clientport;
            int accept_errno = 0; // 获取accept的错误码，以便后续判断处理

            int sock = Sock::Accept(conet->_sock, &clientport, &clientip, &accept_errno);
            if (sock < 0) // accept失败，判断情况
            {
                if (accept_errno == EAGAIN || accept_errno == EWOULDBLOCK) // 非阻塞I/O操作无法立即完成:底层没有新的连接到来
                    break;
                else if (accept_errno == EINTR) // IO过程被信号中断
                    continue;
                else
                { // 来到这里，才是真正的accept失败
                    logMessage(WARNING, "accept error, %d, %s", accept_errno, strerror(accept_errno));
                    break; // 为什么使用break:这里的失败并不影响我们最终通信处理,连接失败了客户端再连接一次即可.
                }
            }

            // 连接事件就绪，不代表读写就绪（客户端不一定立马会发送数据），因此，我们需要将sock托管给TcpServer（epoll && connection）
            if (sock >= 0) // 上述已经判断过，这个条件判断不加也行，这里是为了逻辑完善
            {
                // 此时获取到的是常规IO sock，因此其需要关心的事件就是：读、写、异常
                AddConnection(sock, std::bind(&TcpServer::Receiver, this, std::placeholders::_1),
                              std::bind(&TcpServer::Sender, this, std::placeholders::_1),
                              std::bind(&TcpServer::Expecter, this, std::placeholders::_1));
                logMessage(DEBUG, "accept client, %s:%d, add its to epoll and connection, sock is %d", clientip.c_str(), clientport, sock);
            }
        }
    }

    void Receiver(Connection *conet)
    {
        // 来到这里，我们可以保证的是此时底层一定有读事件到来。
        logMessage(DEBUG, "Receiver be called. sock is %d", conet->_sock);

        const int buffer_num = 1024;
        bool read_error = false; // 用于判断recv读取数据时的错误情况
        while (true)
        {
            char buffer[buffer_num];                                       // 临时缓冲区：因为我们始终会循环读取直到取完本次读事件中的数据，将其拼接到sock的输入缓冲区中，因此这里的大小设置实则影响不大。
            ssize_t n = recv(conet->_sock, buffer, sizeof(buffer) - 1, 0); // 这里，flage虽然设置为0，但实际读取时，一定会是非阻塞读取。
            if (n < 0)                                                     // 读取失败,判断情况
            {
                if (errno == EAGAIN || errno == EWOULDBLOCK)
                    break;
                else if (errno == EINTR)
                    continue;
                else
                { // 来到这里，才是真正的读取出错。交由异常函数处理（即，本轮读取作废不处理）。
                    logMessage(ERROR, "recv error, %d : %s", errno, strerror(errno));
                    conet->_except_cb(conet);
                    read_error = true; // 设置
                    break;
                }
            }
            else if (n == 0)
            {
                // 读取到文件尾，在网络通信中意味着客户端关闭连接，这里我们也统一交由异常回调处理.
                logMessage(DEBUG, "client[%d] quit, server will close its sock: %d", conet->_sock, conet->_sock);
                conet->_except_cb(conet);
                read_error = true;
                break;
            }
            else
            { // 读取成功
                buffer[n] = 0;
                conet->_inbuffer += buffer; // 将读取到的数据存入自己的输入缓冲区中
            }
        }

        // 来到此处，上述recv循环退出。我们读取客户端事件，是为了进行业务处理，因此，需要对拿到的完整数据，进行后续的业务处理。
        if (!read_error) // 只要读取不出错
        {
            logMessage(DEBUG, "conet->_inbuffer[sock:%d]:\n%s", conet->_sock, conet->_inbuffer.c_str());

            // 首先要解决粘包问题：虽然上述解决了单批次读取报文的完整性，但这并不代表这一批次获取到的报文就是独立的。
            std::vector<std::string> messages;
            Decode(conet->_inbuffer, &messages);
            // 来到此处，就能保证读取到的是一个一个的独立、完整的报文，可以进行后续的业务处理
            for (auto &msg : messages) // 如果message为空， 本次循环不会被调用
            {
                _call(conet, msg); // 网络服务器，一般不和上层业务强耦合。
                // TcpServer只需要完成事件派发、处理事件、根据协议获取到数据即可，致于这些数据是用来做什么业务的，不关心。这是上层的事。
                // 扩展：这里，还可以将message封装成Task，然后Push到任务队列中，让线程池处理。
            }
        }
    }

    void Sender(Connection *conet)
    {
        // 来到这里，我们可以保证的是此时底层一定有写事件到来。
        while (true)
        {
            ssize_t n = send(conet->_sock, conet->_outbuffer.c_str(), conet->_outbuffer.size(), 0);
            if (n > 0)                         // On  success,  these  calls  return the number of characters sent.
            {                                  // 将_outbuffer中的数据发送给对方，不一定保证对方接受端就能存储这么多的数据，因此，这里send的返回值用于判断实际发送的数据大小
                conet->_outbuffer.erase(0, n); // 需要将已发送的数据从输出缓冲区中清除（PS：是否需要考虑丢包问题？这是TCP底层缓冲区要做的事，不是我们应用层负责的事）
                if (conet->_outbuffer.empty()) // 缓冲区中全部数据发送完毕
                    break;
            }
            else // On error, -1 is returned, and errno is set appropriately.
            {
                if (errno == EAGAIN || errno == EWOULDBLOCK) // 非阻塞IO：如果系统内核中的发送缓冲区已满，send函数会立即返回一个错误码（EWOULDBLOCK或EAGAIN），表示当前无法发送数据。
                                                             // 实则问题不大，因为我们上层发送缓冲区中保留着需要发送的数据，不过就是再次触发写事件
                    break;
                else if (errno == EINTR) // 被信号中断
                    continue;
                else
                { // 来到这里，才是真正的读取出错。交由异常函数处理（即，本轮读取作废不处理）。
                    logMessage(ERROR, "send error, %d : %s", errno, strerror(errno));
                    conet->_except_cb(conet);
                    break;
                }
            }
        }
        // 来到这里，能保证数据发完了吗？
        // 回答：根据上述break的情况可知，不能确定。能保证的是，来到此处，要么是发送完成，要么是发送条件不满足，需要下此发送。因此这里需要判断输出缓冲区的实际情况。
        if (conet->_outbuffer.empty())
            EnableReadWirte(conet, true, false); // 如果输出缓冲区中无数据，说明发送完成，此时应该关闭写事件(当TCP套接字的发送缓冲区未满时，会根据ET模式、LT模式，触发EPOLLOUT事件）
        else
            EnableReadWirte(conet, true, true); // 如果输出缓冲区中还有数据，继续触发写事件
    }

    // 汇集了服务器里上述种种情况中的异常。
    void Expecter(Connection *conet)
    {
        if (!IsConnectionExist(conet->_sock)) // 说明之前被处理过
            return;

        logMessage(DEBUG, "Excepter: 出现异常事件，回收异常sock, sock is %d", conet->_sock);

        // 1、从epoll中移除
        bool ret = _poll.DelFromEpoll(conet->_sock);
        assert(ret);
        // 2、从_connections映射表中移除
        _connections.erase(conet->_sock);
        // 3、关闭异常套接字
        close(conet->_sock);
        // 4、释放为其申请的Connection对象
        delete conet;
        logMessage(DEBUG, "Excepter: 资源回收完毕");
    }

    // 使能读写：用于修改epoll对一个sock的读写事件的监控
    void EnableReadWirte(Connection *conet, bool readable, bool writeable)
    {
        // 后两个参数：是否关心该sock的读事件/是否关心该sock的写事件
        uint32_t events = ((readable ? EPOLLIN : 0) | (writeable ? EPOLLOUT : 0));
        if (IsConnectionExist(conet->_sock))
        {
            int ret = _poll.ModFromEpoll(conet->_sock, events); // 由于我们在该函数内部 或等(|)了 EPOLLET，此处可以不添加ET模式。
            assert(ret);
        }
    }

private:
    // 网络编程这一套：
    int _listensock;
    int _port;

    // 存储sock的Connection 类：映射表
    std::unordered_map<int, Connection *> _connections; // 建立了一个映射关系，由sock作为key值，就能获取到该sock对应的缓冲区、处理事件的响应函数。
    // 附注：这里我们是直接使用了Connection*的指针，没有使用RAII智能指针进行管理，相应的这就需要我们在编写时设计、考虑得全面一些，有申请就要有释放。

    // 多路转接这一套：底层负责检测sock及其事件
    Epoll _poll;               // epoll实例
    struct epoll_event *_revs; // 存储就绪事件集（结构体数组）
    int _revs_num;             // 就绪事件最大接收值

    callback_t _call; // 函数调用：用于完成服务器业务逻辑
};
